<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <h2>一、定义</h2>
    <div>函数的 this 关键字在 JavaScript 中的表现略有不同，此外，在严格模式和非严格模式之间也会有一些差别</div>
    <br>
    <div>在绝大多数情况下，函数的调用方式决定了 this 的值（运行时绑定）</div>
    <br>
    <div>this 关键字是函数运行时自动生成的一个内部对象，只能在函数内部使用，总指向调用它的对象</div>
    <br>
    <script>
      // function baz() {
      //     // 当前调用栈是：baz
      //     // 因此，当前调用位置是全局作用域
      //     console.log( "baz" );
      //     console.log(this);
      //     bar(); // <-- bar的调用位置
      // }
      // function bar() {
      //     // 当前调用栈是：baz --> bar
      //     // 因此，当前调用位置在baz中
      //     console.log( "bar" );
      //     console.log(this);
      //     foo(); // <-- foo的调用位置
      // }
      // function foo() {
      //     // 当前调用栈是：baz --> bar --> foo
      //     // 因此，当前调用位置在bar中
      //     console.log( "foo" );
      //     console.log(this);
      // }

      // baz(); // <-- baz的调用位置
    </script>
    <div>同时，this在函数执行过程中，this一旦被确定了，就不可以再更改</div>
    <script>
      // var a = 10;
      // var obj = {
      //   a: 20
      // }

      // function fn() {
      //   // this = obj; // 修改this，运行后会报错
      //   console.log(this.a);
      // }

      // fn();
    </script>
    <div>
      <h2>根据不同的使用场合，this有不同的值，主要分为下面几种情况</h2>
      <h4>默认绑定</h4>
      <ul>
        <li>全局环境中定义person函数，内部使用this关键字,this指向window</li>
        <li>严格模式下，不能将全局对象用于默认绑定，this会绑定到undefined，只有函数运行在非严格模式下，默认绑定才能绑定到全局对象</li>
      </ul>
      <script>
        // 全局环境中定义person函数，内部使用this关键字,调用函数的对象在浏览器中位于window，因此this指向window
        // var name = 'Jenny';
        // function person() {
        //     return this.name;
        // }
        // console.log(person());  //Jenny
      </script>
      <h4>隐式绑定</h4>
      <ul>
        <li>作为某个对象的方法调用，这时this就指这个上级对象</li>
        <li>函数中包含多个对象，尽管这个函数是被最外层的对象所调用，this指向的也只是它上一级的对象</li>
      </ul>
      <script>
        // 作为某个对象的方法调用，这时this就指这个上级对象
        // function test() {
        //   console.log(this.x);
        // }

        // var obj = {};
        // obj.x = 1;
        // obj.m = test;

        // obj.m(); // 1


        // 函数中包含多个对象，尽管这个函数是被最外层的对象所调用，this指向的也只是它上一级的对象
        // var o = {
        //     a:10,
        //     b:{
        //         fn:function(){
        //             console.log(this.a); //undefined this的上一级对象为b，b内部并没有a变量的定义，所以输出undefined
        //         }
        //     }
        // }
        // o.b.fn();


        // 特殊例子
        // 此时this指向的是window，这里的大家需要记住，this永远指向的是最后调用它的对象，虽然fn是对象b的方法，但是fn赋值给j时候并没有执行，所以最终指向window
        // var o = {
        //     a:10,
        //     b:{
        //         a:12,
        //         fn:function(){
        //             console.log(this.a); //undefined
        //             console.log(this); //window
        //         }
        //     }
        // }
        // var j = o.b.fn;
        // j();
      </script>
      <h4>new绑定</h4>
      <ul>
        <li>通过构建函数new关键字生成一个实例对象，此时this指向这个实例对象</li>
        <li>new过程遇到return一个对象，此时this指向为返回的对象</li>
        <li>如果返回一个简单类型的时候，则this指向实例对象</li>
        <li>注意的是null虽然也是对象，但是此时new仍然指向实例对象</li>
      </ul>
      <script>
        // 通过构建函数new关键字生成一个实例对象，此时this指向这个实例对象
        // function test() {
        // this.x = 1;
        // }
        // var obj = new test();//是因为new关键字用apply()方法改变了this的指向
        // console.log(obj.x); // 1
        // console.log(this.x); // undefined

        // new过程遇到return一个对象，此时this指向为返回的对象
        // function fn(){  
        //     this.user = 'xxx';  
        //     return {};  
        // }
        // var a = new fn();  
        // console.log(a.user); //undefined

        // 如果返回一个简单类型的时候，则this指向实例对象
        // function fn(){  
        //     this.user = 'xxx';  
        //     return 1;
        // }
        // var a = new fn;
        // console.log(a.user); //xxx

        // 注意的是null虽然也是对象，但是此时new仍然指向实例对象
        // function fn(){  
        //     this.user = 'xxx';  
        //     return null;
        // }
        // var a = new fn;  
        // console.log(a.user); //xxx
      </script>
      <h4>显示修改</h4>
      <ul>
        <li>apply()、call()、bind()是函数的一个方法，作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此，这时this指的就是这第一个参数</li>
      </ul>
      <script>
        // var x = 0;
        // function test() {
        //   console.log(this.x);
        // }
        // var obj = {};
        // obj.x = 1;
        // obj.m = test;
        // obj.m.apply(obj) // 1

        // obj:{
        //   x:1;
        //   m:function test() {
        //     console.log(this.x);
        //   }
        // }
      </script>
      <h4>三、箭头函数</h4>
      <div>绑定事件监听,使用普通函数this指向触发事件的dom,使用箭头函数this指向window</div>
      <br>
      <div>原型上添加方法，使用普通函数this指向构造函数，使用箭头函数this指向window，所以箭头函数不能作为构造函数</div>
      <script>
        // const obj = {
        //   sayThis: () => {
        //     console.log(this);
        //   }
        // };

        // obj.sayThis(); // window 因为 JavaScript 没有块作用域，所以在定义 sayThis 的时候，里面的 this 就绑到 window 上去了
        // const globalSay = obj.sayThis;
        // globalSay(); // window 浏览器中的 global 对象
      </script>
      <h2>四、优先级</h2>
      <div>使用apply(),call(),bind()显示绑定的优先级比印象绑定（对象内绑定）更高</div>
      <br>
      <div>new绑定的优先级>隐式绑定</div>
      <br>
      <div>new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级</div>
      <script>
        // 隐式绑定和显示绑定的对比
        // function foo() {
        //   console.log( this.a );
        // }

        // var obj1 = {
        //     a: 2,
        //     foo: foo
        // };
        // // var obj1 = {
        // //     a: 2,
        // //     foo: function foo() {
        // //       console.log( this.a );
        // //     }
        // // };

        // var obj2 = {
        //     a: 3,
        //     foo: foo
        // };

        // obj1.foo(); // 2
        // obj2.foo(); // 3

        // obj1.foo.call( obj2 ); // 3
        // obj2.foo.call( obj1 ); // 2

        // new绑定和显示绑定对比
        // function foo(something) {
        //     this.a = something;
        // }

        // var obj1 = {};

        // var bar = foo.bind( obj1 );
        // bar( 2 );
        // console.log( obj1.a ); // 2

        // var baz = new bar( 3 );
        // console.log( obj1.a ); // 2
        // console.log( baz.a ); // 3
      </script>
    </div>
  </div>
</body>
</html>